﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using System.IO;
using System.Security.Cryptography;

namespace WAPA.Framework
{
    public static class PackageUpdater
    {
        /// <summary>
        /// Unpacks the SDPack_[GUID].csdx file that contains the servicedefinition and updates the content. 
        /// Next it generates a SHA1 hash and puts it in the [GUID].csman file so the management API can let it succesfully pass the security hash check.
        /// </summary>
        /// <param name="newVMSize">ExtraSmall,Small,Medium,Large,ExtraLarge</param>
        /// <param name="packageFile"></param>
        public static void UpdateVMSize(string newVMSize, string packageFile)
        {
            //Unzip the SDPackage file that is included in the main packagefile.
            string sdPackageFilePath= UnzipFileToFolder(packageFile, @"c:\UpdatedPackage","SDPackage_");

            //TODO: Find out why the hash resulting from the code below is not identical with the one generated by CSPack.
            //////Update the hash in the .csman file
            ////string str = File.ReadAllText(sdPackageFilePath);
            ////Encoder enc = System.Text.Encoding.Unicode.GetEncoder();

            ////string sha1Hash = CalculateSHA1(str, new  UTF8Encoding());

            ////// Create a buffer large enough to hold the string
            ////byte[] unicodeText = new byte[str.Length * 2];
            ////enc.GetBytes(str.ToCharArray(), 0, str.Length, unicodeText, 0, true);

            //////SHA1 sha1 = new SHA1CryptoServiceProvider();
            //////byte[] resultSHA = sha1.ComputeHash(unicodeText);

            //////StringBuilder sbSha = new StringBuilder();
            //////for (int i = 0; i < resultSHA.Length; i++)
            //////{
            //////    sbSha.Append(resultSHA[i].ToString("X2"));
            //////}

            //////MD5 md5 = new MD5CryptoServiceProvider();
            //////byte[] result = md5.ComputeHash(unicodeText);

            //////StringBuilder sb = new StringBuilder();
            //////for (int i = 0; i < result.Length; i++)
            //////{
            //////    sb.Append(result[i].ToString("X2"));
            //////}

            //////// And return it
            //////string hash = sb.ToString();

            //Open the SDPAckage_..... zip. Lookup and extract for the servicedefinition.build.csdef.
            string serviceDefinitionFile = UnzipFileToFolder(sdPackageFilePath, @"c:\UpdatedPackage", "ServiceDefinition.build.csdef");

            //Read the content of the service definition
            string serviceDefinition  = File.ReadAllText(serviceDefinitionFile);

            //Set the vmSize
            string modifiedServiceDefinition = serviceDefinition.Replace("vmsize=\"Large\"","vmsize=\"" + newVMSize + "\"");

            //Write to the originalExtractlocation
            File.WriteAllText(serviceDefinitionFile, modifiedServiceDefinition);

            //Add the modified serviceDefinitionFile to the exracted SDPacka_.... zip file
            ZipFile sdPackageZipFile = new ZipFile(sdPackageFilePath);
            sdPackageZipFile.NameTransform = new ZipNameTransform(Path.GetDirectoryName(serviceDefinitionFile));
            sdPackageZipFile.BeginUpdate();
            sdPackageZipFile.Add(serviceDefinitionFile);
            sdPackageZipFile.CommitUpdate();

            //Add the SDPAckage_..... zip file to the main package file (replacing the original version)
            ZipFile packageZipFile = new ZipFile(packageFile);
            packageZipFile.BeginUpdate();
            packageZipFile.Add(sdPackageFilePath,Path.GetFileName(sdPackageFilePath));
            packageZipFile.CommitUpdate();

        }

        /// <summary>
        /// Extract the file from a zip file that satisfies the searchcriteria provided/
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="folder"></param>
        /// <param name="searchArgument">what the file name should contain to be selected for extraction</param>
        /// <returns></returns>
        public static string UnzipFileToFolder(string filePath, string folder, string searchArgument)
        {
            string fileFoundName = string.Empty;
            string fileFoundPath = string.Empty;

            if (!File.Exists(filePath)) return string.Empty;

            if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);

            using (ZipInputStream s = new ZipInputStream(File.OpenRead(filePath)))
            {
                ZipEntry theEntry;
                while ((theEntry = s.GetNextEntry()) != null)
                {
                    if (theEntry.Name.Contains(searchArgument))
                    {

                        string directoryName = Path.GetDirectoryName(theEntry.Name);
                        fileFoundName = Path.GetFileName(theEntry.Name);

                        // create directory
                        if (directoryName.Length > 0)
                        {
                            Directory.CreateDirectory(directoryName);
                        }

                        if (fileFoundName != String.Empty)
                        {
                            fileFoundPath = Path.Combine(folder, theEntry.Name);
                            using (FileStream streamWriter = File.Create(fileFoundPath))
                            {

                                int size = 2048;
                                byte[] data = new byte[2048];
                                while (true)
                                {
                                    size = s.Read(data, 0, data.Length);
                                    if (size > 0)
                                    {
                                        streamWriter.Write(data, 0, size);
                                    }
                                    else
                                    {
                                        break;
                                    }
                                }
                            }
                        }

                        break;
                    }
                }
            }

            return fileFoundPath;
        }

        //Accepts an encoding format (UTF-8 in the case of definition files) and returns a hash for the file content.
        public static string CalculateSHA1(string text, Encoding enc)
        {
            byte[] buffer = enc.GetBytes(text);
            SHA1CryptoServiceProvider cryptoTransformSHA1 =
            new SHA1CryptoServiceProvider();
            string hash = BitConverter.ToString(
                cryptoTransformSHA1.ComputeHash(buffer)).Replace("-", "");

            return hash;
        }
    }
}
